/*
 *      Copyright 2012-2013 Battams, Derek
 *       
 *       Licensed under the Apache License, Version 2.0 (the "License");
 *       you may not use this file except in compliance with the License.
 *       You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *       Unless required by applicable law or agreed to in writing, software
 *       distributed under the License is distributed on an "AS IS" BASIS,
 *       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *       See the License for the specific language governing permissions and
 *       limitations under the License.
 */
package org.schedulesdirect.grabber;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.fluent.Request;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;
import org.schedulesdirect.api.Config;
import org.schedulesdirect.api.EpgClient;

/**
 * Encapsulates a request being sent to the Schedules Direct JSON service.
 * @author Derek Battams &lt;derek@battams.ca&gt;
 *
 */
public final class JsonRequest {
	static private final Log LOG = LogFactory.getLog(JsonRequest.class);
	/**
	 * The supported actions for a request
	 * @author Derek Battams &lt;derek@battams.ca&gt;
	 *
	 */
	static public enum RequestAction {
		GET,
		ADD,
		DELETE,
		UPDATE
	}
	
	/**
	 * The supported objects for a request
	 * @author Derek Battams &lt;derek@battams.ca&gt;
	 *
	 */
	static public enum RequestObject {
		RANDHASH,
		STATUS,
		HEADENDS,
		PROGRAMS,
		LINEUPS,
		SCHEDULES,
		PASSWORD,
		METADATA,
		MESSAGE
	}

	private String baseUrl;
	private String hash;
	private String userAgent;
	private String targetUrl;
	
	/**
	 * Constructor
	 * @param hash The user's hash secret obtained from the SD service
	 * @param userAgent The user agent string to use for the web request to SD
	 * @param baseUrl The base URL to submit the request to; default used if null
	 */
	public JsonRequest(String hash, String userAgent, String baseUrl) {
		this.hash = hash;
		this.userAgent = userAgent;
		this.baseUrl = baseUrl != null ? baseUrl : Config.DEFAULT_BASE_URL;
		targetUrl = null;
	}
	
	/**
	 * Return's the target URL this request was sent to; is null until the request is actually submitted
	 * @return The URL the request was sent to or null if the request has not been submitted yet
	 */
	public String getTargetUrl() { return targetUrl; }
	
	/**
	 * Submit this request; returns the JSON object response received; only call if the request is expected to return a JSON object in response
	 * @param action The action for the request
	 * @param reqObj The object target for the request
	 * @param reqData The supporting data for the request; this is dependent on the action and obj target specified
	 * @return The JSON object response received from the SD service
	 * @throws IOException Thrown on any IO error encountered
	 */
	public JSONObject submitForJson(RequestAction action, RequestObject reqObj, Object reqData) throws IOException {
		HttpResponse resp = submitRaw(action, reqObj, reqData);
		JSONObject obj = null;
		StatusLine status = resp.getStatusLine();
		if(status.getStatusCode() == 200) {
			InputStream ins = resp.getEntity().getContent();
			try {
				try {
					obj = new JSONObject(IOUtils.toString(ins, "UTF-8"));
					return obj;
				} catch (JSONException e) {
					throw new IOException(e);
				}
			} finally {
				if(obj != null && LOG.isDebugEnabled())
					LOG.debug(String.format("Request '%s' handled by %s", getTargetUrl(), obj.optString("serverID", "UNKNOWN")));
				if(ins != null) ins.close();
			}
		} else
			throw new IOException(String.format("Request failed! [rc=%d; msg=%s]", status.getStatusCode(), status.getReasonPhrase()));
	}

	private HttpResponse submitRaw(RequestAction action, RequestObject reqObj, Object reqData) throws IOException {
		JSONObject o = new JSONObject();
		try {
			o.put("action", action.toString().toLowerCase());
			o.put("object", reqObj.toString().toLowerCase());
			o.put("api", EpgClient.API_VERSION);
			if(reqData != null)
				o.put("request", reqData);
			if(hash != null)
				o.put("randhash", hash);
		} catch(JSONException e) {
			throw new RuntimeException(e);
		}
		URL url = new URL(String.format("%s/handleRequest.php", baseUrl));
		targetUrl = url.toString();
		Request req = Request.Post(targetUrl);
		req.bodyForm(Arrays.asList(new BasicNameValuePair("request", o.toString()), new BasicNameValuePair("submit", "submit")), Charset.forName("UTF-8"));
		req = Config.prepRequest(req, getUserAgent());
		return req.execute().returnResponse();
	}

	/**
	 * @return the baseUrl
	 */
	public String getBaseUrl() {
		return baseUrl;
	}

	/**
	 * @return the hash
	 */
	public String getHash() {
		return hash;
	}

	/**
	 * @return the userAgent
	 */
	public String getUserAgent() {
		return userAgent;
	}
}
